package de.unikoblenz.west.csxpoi;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.OverlayItem;

import de.unikoblenz.west.csxpoi.R;

/**
 * Encapsulates POIs, which are identified by their URI and have a name, a
 * latitude, a longitude, an altitude, and categories.
 */
public class PoiWrapper implements Parcelable {

	/**
	 * the URI of the POI
	 */
	private String mUri = null;

	/**
	 * the name of the POI
	 */
	private String mName = null;

	/**
	 * the type of the POI
	 */
	private String mType = "";

	/**
	 * the latitude of the POI
	 */
	private double mLatitude = 0;

	/**
	 * the longitude of the POI
	 */
	private double mLongitude = 0;

	/**
	 * the altitude of the POI
	 */
	private double mAltitude = 0;

	/**
	 * the start date of the POI
	 */
	private String mStartDate = "";

	/**
	 * the end date of the POI
	 */
	private String mEndDate = "";

	/**
	 * the list of categories of the POI
	 */
	private List<CategoryWrapper> mCategories = new ArrayList<CategoryWrapper>();

	// TODO: equivalents

	/**
	 * flag, whether this poi is drawn and enabled on the map
	 */
	private boolean mActive = true;

	/**
	 * Creates an empty POI.
	 */
	public PoiWrapper() {
		return;
	};

	/**
	 * Creates a POI that is a copy of the specified POI.
	 * 
	 * @param poi
	 *            the POI to copy
	 */
	public PoiWrapper(PoiWrapper poi) {
		setUri(poi.getUri());
		setName(poi.getName());
		setLatitude(poi.getLatitude());
		setLongitude(poi.getLongitude());
		setAltitude(poi.getAltitude());
		setCategories(poi.getCategories());
		setStartDate(poi.getStartDate());
		setEndDate(poi.getEndDate());
	}

	/**
	 * Checks if a POI is timely limited
	 * 
	 * @return true if timely limited
	 */
	public boolean isEvent() {
		if ((!mStartDate.equals("")) || (!mEndDate.equals(""))) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Sets the URI of a POI.
	 * 
	 * @param uri
	 *            the URI to set
	 */
	public void setUri(String uri) {
		mUri = uri;
	}

	/**
	 * Sets the name of the POI.
	 * 
	 * @param name
	 *            the name to set
	 */
	public void setName(String name) {
		mName = name;
	}

	/**
	 * Sets the type of the POI.
	 * 
	 * @param type
	 *            the type to set
	 */
	public synchronized void setType(String type) {
		this.mType = type;
	}
	
	/**
	 * Sets the latitude of the POI.
	 * 
	 * @param latitude
	 *            the latitude to set
	 */
	public void setLatitude(double latitude) {
		mLatitude = latitude;
	}

	/**
	 * Sets the longitude of the POI.
	 * 
	 * @param longitude
	 *            the longitude to set
	 */
	public void setLongitude(double longitude) {
		mLongitude = longitude;
	}

	/**
	 * Sets the altitude of the POI.
	 * 
	 * @param altitude
	 *            the altitude to set
	 */
	public void setAltitude(double altitude) {
		mAltitude = altitude;
	}

	/**
	 * Sets the start date of the POI.
	 * 
	 * @param startDate
	 *            the start date to set
	 */
	public void setStartDate(String startDate) {
		mStartDate = startDate.replaceAll("\t", "");
	}

	/**
	 * Sets the end date of the POI.
	 * 
	 * @param endDate
	 *            the end date to set
	 */
	public void setEndDate(String endDate) {
		mEndDate = endDate.replaceAll("\t", "");
	}

	/**
	 * Adds a category to the POI.
	 * 
	 * @param category
	 *            the category to add
	 */
	public void addCategory(CategoryWrapper category) {
		mCategories.add(category);
	}

	/**
	 * Sets the specified list of categories for the POI.
	 * 
	 * @param categories
	 *            the list of categories to set
	 */
	public void setCategories(List<CategoryWrapper> categories) {
		mCategories = categories;
	}

	/**
	 * Removes the specified category from the POI.
	 * 
	 * @param category
	 *            the category to remove
	 */
	public void removeCategory(CategoryWrapper category) {
		mCategories.remove(category);
	}

	/**
	 * Gets the URI of the POI
	 * 
	 * @return the URI of the POI
	 */
	public String getUri() {
		return mUri;
	}

	/**
	 * Gets the name of the POI.
	 * 
	 * @return the name of the POI
	 */
	public String getName() {
		return mName;
	}

	/**
	 * Gets the type of the POI
	 * 	
	 * @return the type of the POI
	 */
	public synchronized String getType() {
		return mType;
	}

	/**
	 * Gets the latitude of the POI.
	 * 
	 * @return the latitude of the POI
	 */
	public double getLatitude() {
		return mLatitude;
	}

	/**
	 * Gets the longitude of the POI.
	 * 
	 * @return the longitude of the POI
	 */
	public double getLongitude() {
		return mLongitude;
	}

	/**
	 * Gets the altitude of the POI.
	 * 
	 * @return the altitude of the POI
	 */
	public double getAltitude() {
		return mAltitude;
	}

	/**
	 * Gets the start date of the POI.
	 * 
	 * @return String the start date
	 */
	public String getStartDate() {
		return mStartDate;
	}

	/**
	 * Gets the end date of the POI.
	 * 
	 * @return String the end date
	 */
	public String getEndDate() {
		return mEndDate;
	}

	/**
	 * Updates the meta data of the POI via REST.
	 * 
	 * @return true if the update was successful
	 */
	public boolean selfUpdate() {

		try {
			URL url = new URL(Constants.REST_ADDRESS + "pois/" + getUri());
			SAXParserFactory spf = SAXParserFactory.newInstance();
			SAXParser sp = spf.newSAXParser();
			XMLReader xr = sp.getXMLReader();
			PoiResponseHandler poiResponseHandler = new PoiResponseHandler();
			xr.setContentHandler(poiResponseHandler);
			xr.parse(new InputSource(url.openStream()));
			PoiWrapper poi = poiResponseHandler.getPoi();
			if (poi != null) {
				setUri(poi.getUri());
				setName(poi.getName());
				setLatitude(poi.getLatitude());
				setLongitude(poi.getLongitude());
				setAltitude(poi.getAltitude());
				setCategories(poi.getCategories());
				setStartDate(poi.getStartDate());
				setEndDate(poi.getEndDate());
				return true;
			}
			return false;
		} catch (MalformedURLException e) {
			return false;
		} catch (IOException e) {
			return false;
		} catch (SAXException e) {
			return false;
		} catch (ParserConfigurationException e) {
			return false;
		}
	}

	/**
	 * Gets the categories of the POI.
	 * 
	 * @return a copy of the list of categories of the POI
	 */
	public List<CategoryWrapper> getCategories() {
		return new ArrayList<CategoryWrapper>(mCategories);
	}

	/**
	 * isActive is true if this {@link PoiWrapper} has to be drawn on the map.
	 * It should not be drawn if it is an event and in the range of the current
	 * set date.
	 * 
	 * @return boolean true if this {@link PoiWrapper} has to be drawn and
	 *         enabled on the map, false else
	 */
	public boolean isActive() {
		return mActive;
	}

	/**
	 * sets this poi active, so that he can be tapped on the map
	 */
	public void setActive(boolean active) {
		this.mActive = active;
	}

	/**
	 * Gets a map overlay item representing the POI.
	 * 
	 * @return a map overlay item representing the POI
	 */
	public OverlayItem getOverlayItem() {
		if (this.isEvent()) {
			return new EventOverlayItem(new GeoPoint(
					(int) (mLatitude * Constants.MD_FACTOR),
					(int) (mLongitude * Constants.MD_FACTOR)), mName, null);
		} else {
			return new PoiOverlayItem(new GeoPoint(
					(int) (mLatitude * Constants.MD_FACTOR),
					(int) (mLongitude * Constants.MD_FACTOR)), mName, null);
		}
	}

	/**
	 * PoiOverlayItem is the {@link OverlayItem} for all POIs. It has a static
	 * yellow star image as marker.
	 * 
	 * @author Daniel Schmei�
	 * 
	 */
	public class PoiOverlayItem extends OverlayItem {
		public PoiOverlayItem(GeoPoint point, String title, String snippet) {
			super(point, title, snippet);
			
			Drawable marker = null;
			if (getType().equalsIgnoreCase("LGD")) {
				marker = (Drawable) InteractiveMapView.getMapContext()
				.getResources()
				.getDrawable(R.drawable.btn_star_big_on_disable__green);
			}
			else {
				marker = (Drawable) InteractiveMapView.getMapContext()
						.getResources()
						.getDrawable(R.drawable.btn_star_big_on_disable);
			}	marker.setBounds(0, 0, marker.getIntrinsicWidth(),
						marker.getIntrinsicHeight());
				marker.setAlpha(255);
				this.setMarker(marker);
			
		}
	}

	/**
	 * EventOverlayItem is the {@link OverlayItem} for all Events. The blue star
	 * marker image is chosen corresponding to the poi-date and the current
	 * date.
	 * 
	 * @author Daniel Schmei�
	 * 
	 */
	public class EventOverlayItem extends OverlayItem {
		public EventOverlayItem(GeoPoint point, String title, String snippet) {
			super(point, title, snippet);

			Calendar cal = (Calendar) CsxPoiActivity.mCal.clone();
			Calendar poiStartCal = Helpers.getCalendarOfIsoString(mStartDate);
			int deltaStartDays = (int) Helpers.daysBetween(cal, poiStartCal);

			Drawable marker = null;

			// event start date is after the current set date and less then
			// DELTA_DAYS off
			if (deltaStartDays != -1 && deltaStartDays < Constants.DELTA_DAYS) {

				int starIndex = (int) Math.floor(deltaStartDays * 10 / 30);

				switch (starIndex) {
				case 0:
					marker = (Drawable) InteractiveMapView.getMapContext()
							.getResources().getDrawable(R.drawable.btn_star_0);
					break;
				case 1:
					marker = (Drawable) InteractiveMapView.getMapContext()
							.getResources().getDrawable(R.drawable.btn_star_1);
					break;
				case 2:
					marker = (Drawable) InteractiveMapView.getMapContext()
							.getResources().getDrawable(R.drawable.btn_star_2);
					break;
				case 3:
					marker = (Drawable) InteractiveMapView.getMapContext()
							.getResources().getDrawable(R.drawable.btn_star_3);
					break;
				case 4:
					marker = (Drawable) InteractiveMapView.getMapContext()
							.getResources().getDrawable(R.drawable.btn_star_4);
					break;
				case 5:
					marker = (Drawable) InteractiveMapView.getMapContext()
							.getResources().getDrawable(R.drawable.btn_star_5);
					break;
				case 6:
					marker = (Drawable) InteractiveMapView.getMapContext()
							.getResources().getDrawable(R.drawable.btn_star_6);
					break;
				case 7:
					marker = (Drawable) InteractiveMapView.getMapContext()
							.getResources().getDrawable(R.drawable.btn_star_7);
					break;
				case 8:
					marker = (Drawable) InteractiveMapView.getMapContext()
							.getResources().getDrawable(R.drawable.btn_star_8);
					break;
				case 9:
					marker = (Drawable) InteractiveMapView.getMapContext()
							.getResources().getDrawable(R.drawable.btn_star_9);
					break;

				}
				mActive = true;

			}
			// start date is before the current set date, but the end date is
			// after the current date, so the event does currently occur
			else if (deltaStartDays == -1
					&& Helpers.daysBetween(cal,
							Helpers.getCalendarOfIsoString(mEndDate)) != -1) {
				marker = (Drawable) InteractiveMapView.getMapContext()
						.getResources()
						.getDrawable(R.drawable.btn_star_occurring);
				mActive = true;
			}
			// start and end date of the event are before the current set date,
			// so don't show this event
			else {
				marker = (Drawable) InteractiveMapView.getMapContext()
						.getResources().getDrawable(R.drawable.btn_circle);
				marker.setAlpha(0);
				mActive = false;
			}
			marker.setBounds(0, 0, marker.getIntrinsicHeight(),
					marker.getIntrinsicWidth());
			this.setMarker(marker);
		}
	}

	/**
	 * The POI's name is its string representation.
	 */
	@Override
	public String toString() {
		return mName;
	}

	/**
	 * POIs are equal if their URIs match.
	 */
	@Override
	public boolean equals(Object object) {
		try {
			PoiWrapper poi = (PoiWrapper) object;
			return mUri.equals(poi.getUri());
		} catch (ClassCastException e) {
			return false;
		} catch (NullPointerException e) {
			return false;
		}
	}

	/**
	 * Handles the creation of a POI or an array of POIs from a parcel.
	 */
	public static Parcelable.Creator<PoiWrapper> CREATOR = new Creator<PoiWrapper>() {

		/**
		 * Creates a POI from the specified parcel.
		 */
		public PoiWrapper createFromParcel(Parcel source) {
			PoiWrapper poi = new PoiWrapper();
			poi.setUri(source.readString());
			poi.setName(source.readString());
			poi.setLatitude(source.readDouble());
			poi.setLongitude(source.readDouble());
			poi.setAltitude(source.readDouble());
			poi.setStartDate(source.readString());
			poi.setEndDate(source.readString());

			int numCategories = source.readInt();
			while (numCategories > 0) {
				CategoryWrapper category = new CategoryWrapper();
				category.setUri(source.readString());
				category.setName(source.readString());
				poi.addCategory(category);
				numCategories--;
			}
			return poi;
		}

		/**
		 * Creates an empty array of POIs of the specified size.
		 */
		public PoiWrapper[] newArray(int size) {
			PoiWrapper[] pois = new PoiWrapper[size];
			for (int i = 0; i < size; i++) {
				pois[i] = new PoiWrapper();
			}
			return pois;
		}

	};

	/**
	 * Handles the creation of a parcel for the POI.
	 */
	public void writeToParcel(Parcel out, int flags) {
		out.writeString(mUri);
		out.writeString(mName);
		out.writeDouble(mLatitude);
		out.writeDouble(mLongitude);
		out.writeDouble(mAltitude);
		out.writeString(mStartDate);
		out.writeString(mEndDate);

		out.writeInt(mCategories.size());
		for (CategoryWrapper category : mCategories) {
			out.writeString(category.getUri());
			out.writeString(category.getName());
		}
	}

	/**
	 * No special objects are contained.
	 */
	public int describeContents() {
		return 0;
	}
}
